﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection.Emit;
using System.Threading;
using System.Reflection;
//http://www.codeproject.com/Articles/121568/Dynamic-Type-Using-Reflection-Emit
//http://msdn.microsoft.com/en-us/library/62bwd2yd%28VS.80%29.aspx
namespace EmitMethodSample
{
    public class DynamicBuilder
    {

        public AppDomain CurrentDomain
        {
            get { return Thread.GetDomain(); }
        }

        # region Global Methods

        public AssemblyBuilder GetAssemblyBuilder(string assemblyName)
        {
            AssemblyName aname = new AssemblyName(assemblyName);
            AppDomain currentDomain = AppDomain.CurrentDomain; // Thread.GetDomain();
            AssemblyBuilder builder = currentDomain.DefineDynamicAssembly(aname, AssemblyBuilderAccess.RunAndSave);
            return builder;
        }

        public ModuleBuilder GetModule(AssemblyBuilder asmBuilder)
        {
            ModuleBuilder builder = asmBuilder.DefineDynamicModule("EmitMethods", "EmitMethods.dll");
            return builder;
        }

        public EnumBuilder GetEnum(ModuleBuilder modBuilder, string enumName)
        {
            EnumBuilder builder = modBuilder.DefineEnum(enumName, TypeAttributes.Public, typeof(System.Int32));
            return builder;
        }

        public TypeBuilder GetType(ModuleBuilder modBuilder, string className)
        {
            TypeBuilder builder = modBuilder.DefineType(className, TypeAttributes.Public);
            return builder;
        }

        public TypeBuilder GetTypeBuilder(ModuleBuilder modBuilder, string className, params string[] genericparameters)
        {
            TypeBuilder builder = modBuilder.DefineType(className, TypeAttributes.Public);
            GenericTypeParameterBuilder[] genBuilders = builder.DefineGenericParameters(genericparameters);

            foreach (GenericTypeParameterBuilder genBuilder in genBuilders) // We take each generic type T : class, new()
            {
                genBuilder.SetGenericParameterAttributes(GenericParameterAttributes.ReferenceTypeConstraint | GenericParameterAttributes.DefaultConstructorConstraint);
                //genBuilder.SetInterfaceConstraints(interfaces);
            }

            return builder;
        }

        public MethodBuilder GetMethod(TypeBuilder typBuilder, string methodName)
        {
            MethodBuilder builder = typBuilder.DefineMethod(methodName, MethodAttributes.Public | MethodAttributes.HideBySig);
            return builder;
        }
        public MethodBuilder GetMethod(TypeBuilder typBuilder, string methodName, Type returnType, params Type[] parameterTypes)
        {
            MethodBuilder builder = typBuilder.DefineMethod(methodName, MethodAttributes.Public | MethodAttributes.HideBySig, CallingConventions.HasThis, returnType, parameterTypes);
            return builder;
        }

        public MethodBuilder GetMethod(TypeBuilder typBuilder, string methodName, Type returnType, string[] genericParameters, params Type[] parameterTypes)
        {
            MethodBuilder builder = typBuilder.DefineMethod(methodName, MethodAttributes.Public | MethodAttributes.HideBySig, CallingConventions.HasThis, returnType, parameterTypes);

            GenericTypeParameterBuilder[] genBuilders = builder.DefineGenericParameters(genericParameters);

            foreach (GenericTypeParameterBuilder genBuilder in genBuilders) // We take each generic type T : class, new()
            {
                genBuilder.SetGenericParameterAttributes(GenericParameterAttributes.ReferenceTypeConstraint | GenericParameterAttributes.DefaultConstructorConstraint);
                //genBuilder.SetInterfaceConstraints(interfaces);
            }
            return builder;
        }
        # endregion

        public Type CreateMethod()
        {
            AppDomain currentDomain = AppDomain.CurrentDomain;
            AssemblyBuilder asmbuilder = this.GetAssemblyBuilder("MyAssembly");
            ModuleBuilder mbuilder = this.GetModule(asmbuilder);
            TypeBuilder tbuilder = this.GetTypeBuilder(mbuilder, "MyClass");

            Type[] tparams = { typeof(System.Int32), typeof(System.Int32) };
            MethodBuilder methodSum = this.GetMethod(tbuilder, "Sum", typeof(System.Single), tparams);
            ILGenerator generator = methodSum.GetILGenerator();
            generator.Emit(OpCodes.Ldarg_0);
            generator.Emit(OpCodes.Ldarg_1);
            generator.Emit(OpCodes.Add_Ovf);
            generator.Emit(OpCodes.Stloc_0);
            generator.Emit(OpCodes.Br_S);
            generator.Emit(OpCodes.Ldloc_0);
            generator.Emit(OpCodes.Ret);
            
            Type thistype = tbuilder.CreateType();

            return thistype;
        }

        public void CreateBuilder()
        {
            //Step 1 : Create the Assembly
            AssemblyBuilder asmBuilder = this.GetAssemblyBuilder("MyBuilder");

            //Step 2: Add A Module to the Assembly
            ModuleBuilder mbuilder = this.GetModule(asmBuilder);

            //Step 3: Add the Type IBuilder
            Type iBuilder = this.CreateIBuilder(mbuilder);

            //Step 4 : Implement IBuilder to create Builder 
            Type Builder = this.CreateBuilderImpl(mbuilder, iBuilder);

            asmBuilder.Save("MyBuilder.dll");

            dynamic variable = new Builder(20, 10); 
                
            //Activator.CreateInstance(Builder, new object[] { 20, 10 });
            float result = variable.Sum(30, 40);
            Console.WriteLine("Result  for Sum(30, 40) : {0}", result);
            result = variable.Substract(50, 25);
            Console.WriteLine("Result  for Substract(50, 25) : {0}", result);
            result = variable.Multiply(3, 5);
            Console.WriteLine("Result  for Multiply(3, 5) : {0}", result);
            result = variable.Divide(30, 5);
            Console.WriteLine("Result  for Divide(30, 5) : {0}", result);

            Console.Read();
        }

        private Type CreateBuilderImpl(ModuleBuilder mbuilder, Type parentBuilder)
        {

            # region Type
            /*
             .class public auto ansi beforefieldinit EmitMethodSample.Builder
                   extends [mscorlib]System.Object
                   implements EmitMethodSample.IBuilder
            {
            }
             */

            Type[] interfaces = { parentBuilder };
            TypeBuilder tbuilder = mbuilder.DefineType("Builder", TypeAttributes.Public |
                TypeAttributes.AutoClass |
                TypeAttributes.AnsiClass |
                TypeAttributes.BeforeFieldInit,
                typeof(System.Object),
                interfaces);

            # endregion

            # region Property
            /*
              .field private int32 firstNum
              .property instance int32 FirstNum()
                {
                  .set instance void EmitMethodSample.Builder::set_FirstNum(int32)
                  .get instance int32 EmitMethodSample.Builder::get_FirstNum()
                }
                .method public hidebysig specialname instance int32 
                        get_FirstNum() cil managed
                {
                  .custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = ( 01 00 00 00 ) 
                  // Code size       12 (0xc)
                  .maxstack  1
                  .locals init (int32 V_0)
                  IL_0000:  nop
                  IL_0001:  ldarg.0
                  IL_0002:  ldfld      int32 EmitMethodSample.Builder::firstNum
                  IL_0007:  stloc.0
                  IL_0008:  br.s       IL_000a
                  IL_000a:  ldloc.0
                  IL_000b:  ret
                }
                .method public hidebysig specialname instance void 
                        set_FirstNum(int32 'value') cil managed
                {
                  // Code size       9 (0x9)
                  .maxstack  8
                  IL_0000:  nop
                  IL_0001:  ldarg.0
                  IL_0002:  ldarg.1
                  IL_0003:  stfld      int32 EmitMethodSample.Builder::firstNum
                  IL_0008:  ret
                }
             */
            FieldBuilder fFirst = tbuilder.DefineField("firstNum", typeof(System.Int32), FieldAttributes.Private);
            PropertyBuilder pFirst = tbuilder.DefineProperty("FirstNum", PropertyAttributes.HasDefault, typeof(System.Int32), null);
            //Getter
            MethodBuilder mFirstGet = tbuilder.DefineMethod("get_FirstNum", MethodAttributes.Public |
                MethodAttributes.SpecialName |
                MethodAttributes.HideBySig, typeof(System.Int32), Type.EmptyTypes);
            ILGenerator firstGetIL = mFirstGet.GetILGenerator();

            firstGetIL.Emit(OpCodes.Ldarg_0);
            firstGetIL.Emit(OpCodes.Ldfld, fFirst);
            firstGetIL.Emit(OpCodes.Ret);

            //Setter
            MethodBuilder mFirstSet = tbuilder.DefineMethod("set_FirstNum", MethodAttributes.Public |
                MethodAttributes.SpecialName |
                MethodAttributes.HideBySig, null, new Type[] { typeof(System.Int32) });

            ILGenerator firstSetIL = mFirstSet.GetILGenerator();

            firstSetIL.Emit(OpCodes.Ldarg_0);
            firstSetIL.Emit(OpCodes.Ldarg_1);
            firstSetIL.Emit(OpCodes.Stfld, fFirst);
            firstSetIL.Emit(OpCodes.Ret);

            pFirst.SetGetMethod(mFirstGet);
            pFirst.SetSetMethod(mFirstSet);


            //Second Property

            FieldBuilder fSecond = tbuilder.DefineField("secondNum", typeof(System.Int32), FieldAttributes.Private);
            PropertyBuilder pSecond = tbuilder.DefineProperty("SecondNum", PropertyAttributes.HasDefault, typeof(System.Int32), null);
            //Getter
            MethodBuilder mSecondGet = tbuilder.DefineMethod("get_SecondNum", MethodAttributes.Public |
                MethodAttributes.SpecialName |
                MethodAttributes.HideBySig, typeof(System.Int32), Type.EmptyTypes);
            ILGenerator secondGetIL = mSecondGet.GetILGenerator();

            secondGetIL.Emit(OpCodes.Ldarg_0);
            secondGetIL.Emit(OpCodes.Ldfld, fSecond);
            secondGetIL.Emit(OpCodes.Ret);

            //Setter
            MethodBuilder mSecondSet = tbuilder.DefineMethod("set_SecondNum", MethodAttributes.Public |
                MethodAttributes.SpecialName |
                MethodAttributes.HideBySig, null, new Type[] { typeof(System.Int32) });

            ILGenerator secondSetIL = mSecondSet.GetILGenerator();

            secondSetIL.Emit(OpCodes.Ldarg_0);
            secondSetIL.Emit(OpCodes.Ldarg_1);
            secondSetIL.Emit(OpCodes.Stfld, fSecond);
            secondSetIL.Emit(OpCodes.Ret);

            pFirst.SetGetMethod(mSecondGet);
            pFirst.SetSetMethod(mSecondSet);

            # endregion

            # region Constructor
            /* .method public hidebysig specialname rtspecialname 
                        instance void  .ctor(int32 firstnum,
                             int32 secondnum) cil managed
                {
                    // Code size       26 (0x1a)
                    .maxstack  8
                    IL_0000:  ldarg.0
                    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
                    IL_0006:  nop
                    IL_0007:  nop
                    IL_0008:  ldarg.0
                    IL_0009:  ldarg.1
                    IL_000a:  call       instance void EmitMethodSample.Builder::set_FirstNum(int32)
                    IL_000f:  nop
                    IL_0010:  ldarg.0
                    IL_0011:  ldarg.2
                    IL_0012:  call       instance void EmitMethodSample.Builder::set_SecondNum(int32)
                    IL_0017:  nop
                    IL_0018:  nop
                    IL_0019:  ret
                }
             }*/

            Type[] parameters = { typeof(System.Int32), typeof(System.Int32) };
            ConstructorBuilder cBuilder = tbuilder.DefineConstructor(MethodAttributes.Public |
                MethodAttributes.HideBySig |
                MethodAttributes.SpecialName |
                MethodAttributes.RTSpecialName,
                CallingConventions.Standard,
                parameters);

            ConstructorInfo conObj = typeof(object).GetConstructor(new Type[0]);

            ILGenerator cil = cBuilder.GetILGenerator();
            cil.Emit(OpCodes.Ldarg_0);
            cil.Emit(OpCodes.Call, conObj);
            cil.Emit(OpCodes.Nop);
            cil.Emit(OpCodes.Nop);
            cil.Emit(OpCodes.Ldarg_0);
            cil.Emit(OpCodes.Ldarg_1);
            cil.Emit(OpCodes.Call, mFirstSet);
            cil.Emit(OpCodes.Nop);
            cil.Emit(OpCodes.Ldarg_0);
            cil.Emit(OpCodes.Ldarg_1);
            cil.Emit(OpCodes.Call, mSecondSet);
            cil.Emit(OpCodes.Nop);
            cil.Emit(OpCodes.Nop);
            cil.Emit(OpCodes.Ret);

            # endregion

            # region Methods

            # region Divide
            /*.method public hidebysig newslot virtual final 
                        instance float32  Divide(int32 firstnum,
                                 int32 secondnum) cil managed
                {
                  // Code size       39 (0x27)
                  .maxstack  2
                  .locals init (class [mscorlib]System.DivideByZeroException V_0,
                           float32 V_1)
                  IL_0000:  nop
                  .try
                  {
                    IL_0001:  nop
                    IL_0002:  ldarg.1
                    IL_0003:  ldarg.2
                    IL_0004:  div
                    IL_0005:  conv.r4
                    IL_0006:  stloc.1
                    IL_0007:  leave.s    IL_0024
                  }  // end .try
                  catch [mscorlib]System.DivideByZeroException 
                  {
                    IL_0009:  stloc.0
                    IL_000a:  nop
                    IL_000b:  ldstr      "ZeroDivide exception : {0}"
                    IL_0010:  ldloc.0
                    IL_0011:  callvirt   instance string [mscorlib]System.Exception::get_Message()
                    IL_0016:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                                  object)
                    IL_001b:  nop
                    IL_001c:  ldc.r4     0.0
                    IL_0021:  stloc.1
                    IL_0022:  leave.s    IL_0024
                  }  // end handler
                  IL_0024:  nop
                  IL_0025:  ldloc.1
                  IL_0026:  ret
                }
            }*/

            //Divide
            MethodBuilder mDivide = tbuilder.DefineMethod("Divide", MethodAttributes.Public |
                MethodAttributes.HideBySig |
                MethodAttributes.NewSlot |
                MethodAttributes.Virtual |
                MethodAttributes.Final,
                CallingConventions.Standard,
                typeof(System.Single),
                new Type[] { typeof(System.Int32), typeof(System.Int32) });
            mDivide.SetImplementationFlags(MethodImplAttributes.Managed);

            ILGenerator dil = mDivide.GetILGenerator();
            dil.DeclareLocal(typeof(System.DivideByZeroException));
            dil.DeclareLocal(typeof(System.Single));


            dil.Emit(OpCodes.Nop);
            Label lblTry = dil.BeginExceptionBlock();

            dil.Emit(OpCodes.Nop);
            dil.Emit(OpCodes.Ldarg_1);
            dil.Emit(OpCodes.Ldarg_2);
            dil.Emit(OpCodes.Div);
            dil.Emit(OpCodes.Conv_R4); // Converts to Float32
            dil.Emit(OpCodes.Stloc_1);
            dil.Emit(OpCodes.Leave, lblTry);

            dil.BeginCatchBlock(typeof(DivideByZeroException));
            dil.Emit(OpCodes.Stloc_0);
            dil.Emit(OpCodes.Nop);
            dil.Emit(OpCodes.Ldstr, "ZeroDivide exception : {0}");
            dil.Emit(OpCodes.Ldloc_0);
            MethodInfo minfo = typeof(DivideByZeroException).GetMethod("get_Message");
            dil.Emit(OpCodes.Callvirt, minfo);
            MethodInfo wl = typeof(System.Console).GetMethod("WriteLine", new Type[] { typeof(string), typeof(object) });
            dil.Emit(OpCodes.Call, wl);
            dil.Emit(OpCodes.Nop);
            dil.Emit(OpCodes.Ldc_R4, 0.0);
            dil.Emit(OpCodes.Stloc_1);
            dil.Emit(OpCodes.Leave_S, lblTry);

            dil.EndExceptionBlock();
            dil.Emit(OpCodes.Nop);
            dil.Emit(OpCodes.Ldloc_1);
            dil.Emit(OpCodes.Ret);

            # endregion

            # region Sum
            /*.method public hidebysig newslot virtual final 
                instance float32  Sum(int32 firstnum,
                              int32 secondnum) cil managed
            {
              // Code size       10 (0xa)
              .maxstack  2
              .locals init (float32 V_0)
              IL_0000:  nop
              IL_0001:  ldarg.1
              IL_0002:  ldarg.2
              IL_0003:  add
              IL_0004:  conv.r4
              IL_0005:  stloc.0
              IL_0006:  br.s       IL_0008
              IL_0008:  ldloc.0
              IL_0009:  ret
            }*/

            //Sum
            MethodBuilder mSum = tbuilder.DefineMethod("Sum", MethodAttributes.Public |
                MethodAttributes.HideBySig |
                MethodAttributes.NewSlot |
                MethodAttributes.Virtual |
                MethodAttributes.Final,
                CallingConventions.Standard,
                typeof(System.Single),
                new Type[] { typeof(System.Int32), typeof(System.Int32) });
            mSum.SetImplementationFlags(MethodImplAttributes.Managed);
            ILGenerator sil = mSum.GetILGenerator();
            LocalBuilder lbuilder = sil.DeclareLocal(typeof(Single));
            
            Label endofmethodSum = sil.DefineLabel();
            sil.Emit(OpCodes.Nop);
            sil.Emit(OpCodes.Ldarg_1);
            sil.Emit(OpCodes.Ldarg_2);
            sil.Emit(OpCodes.Add);
            sil.Emit(OpCodes.Conv_R4); // Converts to Float32
            sil.Emit(OpCodes.Stloc_0);
            sil.Emit(OpCodes.Br_S, endofmethodSum);
            sil.Emit(OpCodes.Ldloc_0);
            sil.MarkLabel(endofmethodSum);
            sil.Emit(OpCodes.Ret);

            # endregion

            # region Multiply
            /*.method public hidebysig newslot virtual final 
                instance float32  Multiply(int32 firstnum,
                                   int32 secondnum) cil managed
            {
              // Code size       10 (0xa)
              .maxstack  2
              .locals init (float32 V_0)
              IL_0000:  nop
              IL_0001:  ldarg.1
              IL_0002:  ldarg.2
              IL_0003:  mul
              IL_0004:  conv.r4
              IL_0005:  stloc.0
              IL_0006:  br.s       IL_0008
              IL_0008:  ldloc.0
              IL_0009:  ret
            }*/

            //Multiply
            MethodBuilder mMultiply = tbuilder.DefineMethod("Multiply", MethodAttributes.Public |
                MethodAttributes.HideBySig |
                MethodAttributes.NewSlot |
                MethodAttributes.Virtual |
                MethodAttributes.Final,
                CallingConventions.Standard,
                typeof(System.Single),
                new Type[] { typeof(System.Int32), typeof(System.Int32) });
            mMultiply.SetImplementationFlags(MethodImplAttributes.Managed);
            ILGenerator mil = mMultiply.GetILGenerator();
            mil.DeclareLocal(typeof(Single));
            Label endofmethodmul = mil.DefineLabel();
            mil.Emit(OpCodes.Nop);
            mil.Emit(OpCodes.Ldarg_1);
            mil.Emit(OpCodes.Ldarg_2);
            mil.Emit(OpCodes.Mul);
            mil.Emit(OpCodes.Conv_R4); // Converts to Float32
            mil.Emit(OpCodes.Stloc_0);
            mil.Emit(OpCodes.Br_S, endofmethodmul);
            mil.Emit(OpCodes.Ldloc_0);
            mil.MarkLabel(endofmethodmul);
            mil.Emit(OpCodes.Ret);

            # endregion

            # region Multiply
            /*.method public hidebysig newslot virtual final 
                instance float32  Substract(int32 firstnum,
                                    int32 secondnum) cil managed
            {
              // Code size       10 (0xa)
              .maxstack  2
              .locals init (float32 V_0)
              IL_0000:  nop
              IL_0001:  ldarg.1
              IL_0002:  ldarg.2
              IL_0003:  sub
              IL_0004:  conv.r4
              IL_0005:  stloc.0
              IL_0006:  br.s       IL_0008
              IL_0008:  ldloc.0
              IL_0009:  ret
            }*/

            //Multiply
            MethodBuilder mSubstract = tbuilder.DefineMethod("Substract", MethodAttributes.Public |
                MethodAttributes.HideBySig |
                MethodAttributes.NewSlot |
                MethodAttributes.Virtual |
                MethodAttributes.Final,
                CallingConventions.Standard,
                typeof(System.Single),
                new Type[] { typeof(System.Int32), typeof(System.Int32) });
            mSubstract.SetImplementationFlags(MethodImplAttributes.Managed);
            ILGenerator sbil = mSubstract.GetILGenerator();
            sbil.DeclareLocal(typeof(Single));
            Label endofmethodsubstract = sbil.DefineLabel();
            sbil.Emit(OpCodes.Nop);
            sbil.Emit(OpCodes.Ldarg_1);
            sbil.Emit(OpCodes.Ldarg_2);
            sbil.Emit(OpCodes.Sub);
            sbil.Emit(OpCodes.Conv_R4); // Converts to Float32
            sbil.Emit(OpCodes.Stloc_0);
            sbil.Emit(OpCodes.Br_S, endofmethodsubstract);
            sbil.Emit(OpCodes.Ldloc_0);
            sbil.MarkLabel(endofmethodsubstract);
            sbil.Emit(OpCodes.Ret);

            # endregion

            #endregion

            return tbuilder.CreateType();
        }

        private Type CreateIBuilder(ModuleBuilder mbuilder)
        {
            //Generate IL for 
            /*.class interface public abstract auto ansi EmitMethodSample.IBuilder
            {
                .method public hidebysig newslot abstract virtual 
                                instance float32  Divide(int32 firstnum,
                                 int32 secondnum) cil managed
                {
                }
                .method public hidebysig newslot abstract virtual 
                             instance float32  Sum(int32 firstnum,
                              int32 secondnum) cil managed
                {
                }
                .method public hidebysig newslot abstract virtual 
                                instance float32  Multiply(int32 firstnum,
                                   int32 secondnum) cil managed
                {
                }
                .method public hidebysig newslot abstract virtual 
                                instance float32  Substract(int32 firstnum,
                                    int32 secondnum) cil managed
                {
                }
                
            } */

            TypeBuilder tbuilder = mbuilder.DefineType("IBuilder", TypeAttributes.Interface |
                TypeAttributes.Public |
                TypeAttributes.Abstract |
                TypeAttributes.AutoClass |
                TypeAttributes.AnsiClass);

            //Define Divide
            Type[] tparams = { typeof(System.Int32), typeof(System.Int32) };
            MethodBuilder metDivide = tbuilder.DefineMethod("Divide", MethodAttributes.Public |
                MethodAttributes.Abstract |
                MethodAttributes.Virtual |
                MethodAttributes.HideBySig |
                MethodAttributes.NewSlot,
                CallingConventions.HasThis,
                typeof(System.Single), tparams);
            metDivide.SetImplementationFlags(MethodImplAttributes.Managed);

            MethodBuilder metSum = tbuilder.DefineMethod("Sum", MethodAttributes.Public |
               MethodAttributes.Abstract |
               MethodAttributes.Virtual |
               MethodAttributes.HideBySig |
               MethodAttributes.NewSlot,
               CallingConventions.HasThis,
               typeof(System.Single), tparams);
            metSum.SetImplementationFlags(MethodImplAttributes.Managed);

            MethodBuilder metMultiply = tbuilder.DefineMethod("Multiply", MethodAttributes.Public |
               MethodAttributes.Abstract |
               MethodAttributes.Virtual |
               MethodAttributes.HideBySig |
               MethodAttributes.NewSlot,
               CallingConventions.HasThis,
               typeof(System.Single), tparams);
            metMultiply.SetImplementationFlags(MethodImplAttributes.Managed);

            MethodBuilder metSubstract = tbuilder.DefineMethod("Substract", MethodAttributes.Public |
               MethodAttributes.Abstract |
               MethodAttributes.Virtual |
               MethodAttributes.HideBySig |
               MethodAttributes.NewSlot,
               CallingConventions.HasThis,
               typeof(System.Single), tparams);
            metSubstract.SetImplementationFlags(MethodImplAttributes.Managed);

            Type tIBuilder = tbuilder.CreateType();


            return tIBuilder;
        }


    }
}
